// FacebookRequestParameterList.cs
// Facebook/Framework/Service/Core
// Copyright (c) 2007, Nikhil Kothari. All Rights Reserved.
//

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Globalization;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;

namespace Facebook.Service.Core {

    /// <summary>
    /// Represents a collection of name/value pairs, where each pair corresponds to a parameter
    /// within requests sent to the Facebook service.
    /// </summary>
    public sealed class FacebookRequestParameterList : Dictionary<string, string> {

        private string _blobName;
        private Stream _blobStream;
        private string _blobContentType;

        private void InitializeFormRequest(HttpWebRequest request) {
            StringBuilder requestBuilder = new StringBuilder(512);
            foreach (KeyValuePair<string, string> param in this) {
                if (requestBuilder.Length != 0) {
                    requestBuilder.Append("&");
                }

                requestBuilder.Append(param.Key);
                requestBuilder.Append("=");
                requestBuilder.Append(HttpUtility.UrlEncode(param.Value));
            }

            byte[] requestBytes = Encoding.UTF8.GetBytes(requestBuilder.ToString());

            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = requestBytes.Length;

            using (Stream requestStream = request.GetRequestStream()) {
                requestStream.Write(requestBytes, 0, requestBytes.Length);
            }
        }

        private void InitializeMultiPartRequest(HttpWebRequest request) {
            string boundary = new Guid().ToString("D");

            StringBuilder headerBuilder = new StringBuilder(1024);
            foreach (KeyValuePair<string, string> param in this) {
                headerBuilder.Append("--");
                headerBuilder.Append(boundary);
                headerBuilder.Append("\r\n");
                headerBuilder.Append("Content-Disposition: form-data; name=\"");
                headerBuilder.Append(param.Key);
                headerBuilder.Append("\"\r\n\r\n");
                headerBuilder.Append(param.Value);
                headerBuilder.Append("\r\n");
            }

            headerBuilder.Append("--");
            headerBuilder.Append(boundary);
            headerBuilder.Append("\r\n");
            headerBuilder.Append("Content-Disposition: form-data; filename=\"");
            headerBuilder.Append(_blobName);
            headerBuilder.Append("\"\r\n");
            headerBuilder.Append("Content-Type: ");
            headerBuilder.Append(_blobContentType);
            headerBuilder.Append("\r\n\r\n");

            string header = headerBuilder.ToString();
            string footer = "\r\n--" + boundary;

            byte[] headerBytes = Encoding.UTF8.GetBytes(header);
            byte[] footerBytes = Encoding.UTF8.GetBytes(footer);

            request.ContentType = "multipart/form-data; boundary=" + boundary;
            request.ContentLength = headerBytes.Length + (int)_blobStream.Length + footerBytes.Length;

            using (Stream requestStream = request.GetRequestStream()) {
                requestStream.Write(headerBytes, 0, headerBytes.Length);

                byte[] buffer = new byte[1024];
                int bytesRead = 0;

                while ((bytesRead = _blobStream.Read(buffer, 0, 1024)) != 0) {
                    requestStream.Write(buffer, 0, bytesRead);
                }

                requestStream.Write(footerBytes, 0, footerBytes.Length);
            }
        }

        internal void InitializeRequest(HttpWebRequest request, string method, FacebookSession session, bool useSessionKey) {
            SetRequestMetadata(method, session, useSessionKey);

            request.Method = "POST";
            if (_blobName == null) {
                InitializeFormRequest(request);
            }
            else {
                InitializeMultiPartRequest(request);
            }
        }

        private string CreateSignature(string secret) {
            List<string> paramList = new List<string>(Keys);
            paramList.Sort();

            StringBuilder sigBuilder = new StringBuilder();
            foreach (string param in paramList) {
                sigBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0}={1}", param, this[param]);
            }
            sigBuilder.Append(secret);

            MD5 md5 = MD5.Create();
            byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(sigBuilder.ToString()));

            sigBuilder = new StringBuilder();
            for (int i = 0; i < hash.Length; i++) {
                sigBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0:x2}", hash[i]);
            }

            return sigBuilder.ToString();
        }

        internal void SetBlobParameter(string blobName, Stream blobStream, string blobContentType) {
            Debug.Assert(String.IsNullOrEmpty(blobName) == false);
            Debug.Assert(String.IsNullOrEmpty(blobContentType) == false);
            Debug.Assert(blobStream != null);
            Debug.Assert(blobStream.CanSeek);

            Debug.Assert(_blobName == null);

            _blobName = blobName;
            _blobStream = blobStream;
            _blobContentType = blobContentType;
        }

        private void SetRequestMetadata(string method, FacebookSession session, bool useSessionKey) {
            if (useSessionKey) {
                this["session_key"] = session.SessionKey;
            }
            this["v"] = session.Version;
            this["api_key"] = session.ApplicationKey;

            this["method"] = method;

            this["format"] = "JSON";
            this["call_id"] = DateTime.Now.Ticks.ToString("x", CultureInfo.InvariantCulture);

            this["sig"] = CreateSignature(session.Secret);
        }
    }
}
